package com.cloudkinto.service.workOrder.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.workOrder.WorkOrderAddBo;
import com.cloudkinto.bo.workOrder.WorkOrderQueryBo;
import com.cloudkinto.bo.workOrder.WorkOrderUpdateBo;
import com.cloudkinto.bo.workOrder.type.WorkOrderTypeQueryBo;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.RedisKeyConstants;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.WorkOrderDao;
import com.cloudkinto.entity.*;
import com.cloudkinto.enumType.operate.OperateTypeEnum;
import com.cloudkinto.enumType.workOrder.WorkOrderReplyOriginEnum;
import com.cloudkinto.enumType.workOrder.WorkOrderStatusEnum;
import com.cloudkinto.enumType.workOrder.WorkOrderUrgencyEnum;
import com.cloudkinto.service.company.CompanyService;
import com.cloudkinto.service.cost.CostService;
import com.cloudkinto.service.cost.vo.CostWorkAddReq;
import com.cloudkinto.service.cost.vo.CostWorkItemAddReq;
import com.cloudkinto.service.file.FileService;
import com.cloudkinto.service.operate.OperateRecordService;
import com.cloudkinto.service.user.UserService;
import com.cloudkinto.service.warehouse.WarehouseService;
import com.cloudkinto.service.workOrder.WorkOrderCostService;
import com.cloudkinto.service.workOrder.WorkOrderReplyService;
import com.cloudkinto.service.workOrder.WorkOrderService;
import com.cloudkinto.service.workOrder.WorkOrderTypeService;
import com.cloudkinto.vo.file.FileVo;
import com.cloudkinto.vo.file.WorkOrderFileVo;
import com.cloudkinto.vo.workOrder.WorkOrderDetail;
import com.cloudkinto.vo.workOrder.WorkOrderReplyVo;
import com.cloudkinto.vo.workOrder.WorkOrderVo;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 工单 service实现类
 * </p>
 *
 * @author long
 * @since 2025-01-07
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderDao, WorkOrderDo> implements WorkOrderService {

    private final RedisTemplate redisTemplate;
    private final OperateRecordService operateRecordService;
    private final FileService fileService;
    private final WorkOrderReplyService workOrderReplyService;
    private final WorkOrderTypeService workOrderTypeService;
    private final WorkOrderCostService workOrderCostService;
    private final UserService userService;
    private final CompanyService companyService;
    private final WarehouseService warehouseService;
    private final CostService costService;

    @Override
    public int add(WorkOrderAddBo req, Long userId, String username) {
//        WorkOrderDo entityDo = new WorkOrderDo();
//        BeanUtils.copyProperties(req, entityDo);
        WorkOrderDo entityDo = BeanConvert.INSTANCE.workOrderAddBoToWorkOrderDo(req);
        String workNo = this.generateWorkNo();
        entityDo.setWorkNo(workNo);
        if (StringUtils.isNotBlank(req.getFileIds())) {
            //上传到正式目录
            fileService.uploadFileMult(req.getFileIds().split(","));
        }
        baseMapper.insert(entityDo);
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Submit,
                "",
                entityDo.getId(),
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return 1;
    }

    /**
     * 生成工单号
     * 生成规则：WO+年月日+三位的序列号
     */
    private String generateWorkNo() {
        // 每天重置工单序号
        //生成工单号
        String workOrderNo = String.valueOf(redisTemplate.opsForValue().increment(RedisKeyConstants.WORK_ORDER_NO_SEQ));
        //序列号不足三位在前面补0
        if (workOrderNo.length() < 3) {
            workOrderNo = "000".substring(0, 3 - workOrderNo.length()) + workOrderNo;
        }
        return "WO"
                + DateUtil.format(DateUtil.date(), "yyMMdd")
                + "-"
                + workOrderNo;
    }

    @Override
    public int update(WorkOrderUpdateBo req, Long userId, String username) {
        WorkOrderDo oldDo = this.getById(req.getId());
//        WorkOrderDo newDo = new WorkOrderDo();
//        BeanUtils.copyProperties(req, newDo);
        WorkOrderDo newDo = BeanConvert.INSTANCE.workOrderUpdateBoToWorkOrderDo(req);
        newDo.setCustomerReception(Boolean.FALSE);
        newDo.setStatus(WorkOrderStatusEnum.zero.getValue());
        if (StrUtil.isNotBlank(newDo.getFileIds())) {
            //上传到正式目录
            fileService.uploadFileMult(newDo.getFileIds().split(","));
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Update,
                "",
                newDo.getId(),
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        if (WorkOrderStatusEnum.three.getValue().equals(oldDo.getStatus())) {
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Work_Order_Submit,
                    "",
                    newDo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.WorkOrder
            );
        }
        return baseMapper.updateById(newDo);
    }

    @Override
    public WorkOrderDetail detail(Long id) {
        WorkOrderDo entityDo = baseMapper.selectById(id);
//        WorkOrderDetail detail = new WorkOrderDetail();
//        BeanUtils.copyProperties(entityDo, detail);
        WorkOrderDetail detail = BeanConvert.INSTANCE.workOrderDoToWorkOrderDetail(entityDo);
        CompanyDo companyDo = companyService.getById(entityDo.getCompanyId());
        WarehouseDo warehouseDo = warehouseService.getById(entityDo.getWhId());
        detail.setWhNameCode(warehouseDo.getWhNameCn() + "(" + warehouseDo.getWhCode() + ")");
        detail.setCompanyNameCode(companyDo.getName() + "(" + companyDo.getCompanyCode() + ")");
        detail.setUrgencyStr(WorkOrderUrgencyEnum.getDescByCode(entityDo.getUrgency()));
        detail.setStatus(WorkOrderStatusEnum.getDescByCode(entityDo.getStatus()));
        List<WorkOrderReplyDo> replyDoList = workOrderReplyService.lambdaQuery().eq(WorkOrderReplyDo::getWorkOrderId, id).list();
        Map<Long, UserDo> userIdMatch;
        Map<String, List<FileVo>> fileVoMap = new HashMap<>(4);
        if (StrUtil.isNotEmpty(entityDo.getFileIds())) {
            fileVoMap.computeIfAbsent(
                    WorkOrderReplyOriginEnum.one.getDesc(),
                    key -> new ArrayList<>()).addAll(fileService.getFileVoByIds(entityDo.getFileIds()));
        }
        if (CollUtil.isNotEmpty(replyDoList)) {
            userIdMatch = userService.listByIds(replyDoList.stream().map(WorkOrderReplyDo::getUserId).collect(Collectors.toSet())).stream().collect(Collectors.toMap(UserDo::getId, val -> val));
            for (WorkOrderReplyDo replyDo : replyDoList) {
                WorkOrderReplyVo vo = JSONUtil.toBean(JSONUtil.toJsonStr(replyDo), WorkOrderReplyVo.class);
                if (StrUtil.isNotEmpty(replyDo.getFileIds())) {
                    List<FileVo> fileVoList = fileService.getFileVoByIds(replyDo.getFileIds());
                    fileVoMap.computeIfAbsent(
                            WorkOrderReplyOriginEnum.getDescByCode(replyDo.getOrigin()),
                            key -> new ArrayList<>()).addAll(fileVoList);
                    vo.setFileList(fileVoList);
                }
                vo.setUsername(userIdMatch.get(replyDo.getUserId()).getUserName());
                detail.getReplyList().add(vo);
            }
        }
        if (CollUtil.isNotEmpty(fileVoMap)) {
            for (Map.Entry<String, List<FileVo>> entry : fileVoMap.entrySet()) {
                List<WorkOrderFileVo> workOrderFileVoList = JSONUtil.toList(
                        JSONUtil.toJsonStr(entry.getValue()), WorkOrderFileVo.class);
                workOrderFileVoList.forEach(fileVo -> fileVo.setFileSource(entry.getKey()));
                detail.getFileList().addAll(workOrderFileVoList);
            }
        }
        detail.getFileList().sort(Comparator.comparing(WorkOrderFileVo::getCreateTime).reversed());
        detail.setFileQuantity(detail.getFileList().size());
        detail.getReplyList().sort(Comparator.comparing(WorkOrderReplyVo::getCreateTime).reversed());
        detail.setOperateList(operateRecordService.getOperateList(id, OperateTypeEnum.WorkOrder));
        detail.setCostList(workOrderCostService.lambdaQuery().eq(WorkOrderCostDo::getWorkOrderId, id).list());
        this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, null)
                .update();
        return detail;
    }

    @Override
    public boolean cancel(Long id, Long userId, String username) {
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Cancel,
                "",
                id,
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, null)
                .set(WorkOrderDo::getStatus, WorkOrderStatusEnum.three.getValue())
                .update();
    }

    @Override
    public boolean refuse(Long id, Long userId, String username) {
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Refuse,
                "",
                id,
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, Boolean.TRUE)
                .set(WorkOrderDo::getStatus, WorkOrderStatusEnum.three.getValue())
                .update();
    }

    @Override
    public boolean approvePass(Long id, Long userId, String username) {
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Agree,
                "",
                id,
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, Boolean.TRUE)
                .set(WorkOrderDo::getStatus, WorkOrderStatusEnum.one.getValue())
                .update();
    }

    @Override
    public boolean solveFinish(Long id, Long userId, String username) {
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Solve_Finish,
                "",
                id,
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, Boolean.TRUE)
                .set(WorkOrderDo::getStatus, WorkOrderStatusEnum.two.getValue())
                .update();
    }

    @Override
    public boolean settlementWorkOrder(Long id, Long tenantId, Long userId, String username) {
        List<WorkOrderCostDo> costDoList = workOrderCostService.lambdaQuery()
                .eq(WorkOrderCostDo::getWorkOrderId, id)
                .list();
        if (CollUtil.isNotEmpty(costDoList)) {
            WorkOrderDo workOrderDo = this.getById(id);
            costService.costWordAdd(this.buildCostWorkAddReq(workOrderDo, costDoList), userId, tenantId);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Work_Order_Settlement_Finish,
                "",
                id,
                userId,
                username,
                OperateTypeEnum.WorkOrder
        );
        return this.lambdaUpdate()
                .eq(WorkOrderDo::getId, id)
                .set(WorkOrderDo::getCustomerReception, Boolean.TRUE)
                .set(WorkOrderDo::getSettleFlag, Boolean.TRUE)
                .set(WorkOrderDo::getFinishTime, DateUtil.date())
                .update();
    }

    private CostWorkAddReq buildCostWorkAddReq(WorkOrderDo workOrderDo, List<WorkOrderCostDo> costDoList) {
        CostWorkAddReq req = new CostWorkAddReq();
        req.setOrderType(workOrderDo.getType());
        req.setOrderId(workOrderDo.getId());
        req.setOrderNo(workOrderDo.getWorkNo());
        req.setWhId(workOrderDo.getWhId());
        req.setCompanyId(workOrderDo.getCompanyId());
        req.setOmsOrderCreateTime(workOrderDo.getCreateTime());
        req.setCostTotal(costDoList.stream().map(WorkOrderCostDo::getItemFee).reduce(BigDecimal::add).orElse(BigDecimal.ZERO));
        req.setCurrencyCode(costDoList.get(0).getCurrencyCode());
        req.setStatus(0);
        List<CostWorkItemAddReq> items = new ArrayList<>(costDoList.size());
        for (WorkOrderCostDo costDo : costDoList) {
            CostWorkItemAddReq item = new CostWorkItemAddReq();
            item.setItemName(costDo.getItemName());
            item.setCallDetail(costDo.getBillingVariable());
            item.setCalVolume(costDo.getQty().doubleValue());
            item.setTotalFee(costDo.getItemFee().doubleValue());
            items.add(item);
        }
        req.setItems(items);
        return req;
    }

    @Override
    public SingleResult pageInit(Long userId, Long tenantId, Long companyId, String source) {
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("workOrderTypeList", workOrderTypeService.getList(WorkOrderTypeQueryBo.builder().enable(Boolean.TRUE).tenantId(tenantId).build()));
        map.put("workOrderUrgencyList", WorkOrderUrgencyEnum.getWorkOrderUrgencyList());
        if ("oms".equals(source)) {
            map.put("warehouseList", warehouseService.getWarehouseCompany(companyId, tenantId));
        } else {
            map.put("companyList", companyService.getCompanyList(userId, tenantId, source));
            map.put("warehouseList", warehouseService.getList(tenantId));
        }
        map.put("statusList", WorkOrderStatusEnum.getWorkOrderStatusList());
        return SingleResult.success(map);
    }

    @Override
    public PageResult<WorkOrderVo> getListPage(WorkOrderQueryBo bo) {
        IPage<WorkOrderDo> page = new Page<>(bo.getCurrent(), bo.getPageSize());
        page = this.page(page, this.queryBuild(bo));
        List<WorkOrderVo> resList = handlerListAfter(page.getRecords());
        return new PageResult<>(resList, (int) page.getCurrent(), (int) page.getSize(), (int) page.getTotal(), (int) page.getPages());
    }

    @Override
    public SingleResult<Map<Integer, Integer>> listPageStatusSta(WorkOrderQueryBo bo) {
        List<WorkOrderDo> list = this.list(this.queryBuild(bo));
        Map<Integer, List<WorkOrderDo>> statusMap = list.stream().collect(Collectors.groupingBy(WorkOrderDo::getStatus));
        Map<Integer, Integer> map = new HashMap<>(8);
        for (WorkOrderStatusEnum statusEnum : WorkOrderStatusEnum.values()) {
            map.put(statusEnum.getValue(), statusMap.getOrDefault(statusEnum.getValue(), Collections.emptyList()).size());
        }
        return SingleResult.success(map);
    }

    /**
     * 查询分页列表之后对列表数据的处理
     */
    private List<WorkOrderVo> handlerListAfter(List<WorkOrderDo> list) {
        List<WorkOrderVo> voList = new ArrayList<>();
        if (CollUtil.isNotEmpty(list)) {
            Map<Long, CompanyDo> companyIdMatch = companyService.lambdaQuery()
                    .in(CompanyDo::getId, list.stream().map(WorkOrderDo::getCompanyId).collect(Collectors.toSet()))
                    .list().stream().collect(Collectors.toMap(CompanyDo::getId, val -> val));
            Map<Long, WarehouseDo> whIdMatch = warehouseService.lambdaQuery()
                    .in(WarehouseDo::getId, list.stream().map(WorkOrderDo::getWhId).collect(Collectors.toSet()))
                    .list().stream().collect(Collectors.toMap(WarehouseDo::getId, val -> val));
            for (WorkOrderDo workOrderDo : list) {
//                WorkOrderVo vo = new WorkOrderVo();
//                BeanUtils.copyProperties(workOrderDo, vo);
                WorkOrderVo vo = BeanConvert.INSTANCE.workOrderDoToWorkOrderVo(workOrderDo);
                WarehouseDo warehouseDo = whIdMatch.get(workOrderDo.getWhId());
                if (warehouseDo == null) {
                    vo.setWhNameCode("");
                } else {
                    vo.setWhNameCode(warehouseDo.getWhNameCn() + "(" + warehouseDo.getWhCode() + ")");
                }
                CompanyDo companyDo = companyIdMatch.get(workOrderDo.getCompanyId());
                if (companyDo == null) {
                    vo.setCompanyNameCode("");
                } else {
                    vo.setCompanyNameCode(companyDo.getName() + "(" + companyDo.getCompanyCode() + ")");
                }
                vo.setUrgency(WorkOrderUrgencyEnum.getDescByCode(workOrderDo.getUrgency()));
                vo.setStatus(WorkOrderStatusEnum.getDescByCode(workOrderDo.getStatus()));
                voList.add(vo);
            }
        }
        return voList;
    }

    private LambdaQueryWrapper<WorkOrderDo> queryBuild(WorkOrderQueryBo bo) {
        LambdaQueryWrapper<WorkOrderDo> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Objects.nonNull(bo.getTenantId()), WorkOrderDo::getTenantId, bo.getTenantId());
        lqw.eq(Objects.nonNull(bo.getCompanyId()), WorkOrderDo::getCompanyId, bo.getCompanyId());
        lqw.in(CollUtil.isNotEmpty(bo.getCompanyIdList()), WorkOrderDo::getCompanyId, bo.getCompanyIdList());
        lqw.eq(Objects.nonNull(bo.getWhId()), WorkOrderDo::getWhId, bo.getWhId());
        lqw.in(CollUtil.isNotEmpty(bo.getWhIdList()), WorkOrderDo::getWhId, bo.getWhIdList());
        lqw.eq(Objects.nonNull(bo.getStatus()), WorkOrderDo::getStatus, bo.getStatus());
        lqw.eq(StrUtil.isNotBlank(bo.getWorkOrderType()), WorkOrderDo::getType, bo.getWorkOrderType());
        lqw.eq(Objects.nonNull(bo.getWorkOrderUrgency()), WorkOrderDo::getUrgency, bo.getWorkOrderUrgency());
        lqw.eq(Objects.nonNull(bo.getHasAttach()), WorkOrderDo::getHasAttach, bo.getHasAttach());
        lqw.ge(
                Objects.nonNull(bo.getTimeType()) && bo.getTimeType().equals(1) && Objects.nonNull(bo.getStartDate()),
                WorkOrderDo::getCreateTime,
                bo.getStartDate()
        );
        lqw.le(
                Objects.nonNull(bo.getTimeType()) && bo.getTimeType().equals(1) && Objects.nonNull(bo.getEndDate()),
                WorkOrderDo::getCreateTime,
                bo.getEndDate()
        );
        lqw.ge(
                Objects.nonNull(bo.getTimeType()) && bo.getTimeType().equals(2) && Objects.nonNull(bo.getStartDate()),
                WorkOrderDo::getFinishTime,
                bo.getStartDate()
        );
        lqw.le(
                Objects.nonNull(bo.getTimeType()) && bo.getTimeType().equals(2) && Objects.nonNull(bo.getEndDate()),
                WorkOrderDo::getFinishTime,
                bo.getEndDate()
        );
        lqw.eq(StrUtil.isNotBlank(bo.getWorkNo()), WorkOrderDo::getWorkNo, bo.getWorkNo());
        lqw.like(StrUtil.isNotBlank(bo.getTitle()), WorkOrderDo::getTitle, bo.getTitle());
        lqw.eq(StrUtil.isNotBlank(bo.getSku()), WorkOrderDo::getSku, bo.getSku());
        lqw.eq(Objects.nonNull(bo.getReplyFlag()), WorkOrderDo::getCustomerReception, bo.getReplyFlag());
        if (StrUtil.isNotBlank(bo.getSortName())) {
            lqw.last("order by " + bo.getSortName() + " " + bo.getSortOrder() + ",id " + " " + bo.getSortOrder());
        } else {
            lqw.orderByDesc(WorkOrderDo::getCreateTime, WorkOrderDo::getId);
        }
        return lqw;
    }
}
